package code.google.jcustomize;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.ExpressionEvaluator;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.VariableResolver;

import org.apache.commons.el.ExpressionEvaluatorImpl;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

class TextConfig extends AbstractConfig {
    private boolean USE_FS_PATH     = false;
    private Map items = new LinkedHashMap();
    private String path; 
    private String encoding;

    public boolean patch(ConfigSource cs, VariableResolver variableResolver, FunctionMapper functionMapper) {
        String tempFileName = cs.createTempFile(path, "cfg"+getName().charAt(0), USE_FS_PATH);
        try {
            OutputStream tempW = new BufferedOutputStream(cs.fileConfigWrite(tempFileName, false, true));
            InputStream tempR = new BufferedInputStream(cs.fileConfigRead(path, USE_FS_PATH));
            BufferedWriter w = new BufferedWriter(encoding != null ? new OutputStreamWriter(tempW, encoding) : new OutputStreamWriter(tempW));
            BufferedReader r = new BufferedReader(encoding != null ? new InputStreamReader(tempR, encoding) : new InputStreamReader(tempR));
            boolean result = patch(r, w, variableResolver, functionMapper);
            w.flush();
            w.close();
            r.close();
            if (result)
                cs.safeRename(tempFileName, path, true, USE_FS_PATH);
            return result;
        } catch (FileNotFoundException fnfe) {
            fnfe.printStackTrace();
        } catch (UnsupportedEncodingException uee) {
            uee.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        return false;
    }

    private boolean patch(BufferedReader r, BufferedWriter w, VariableResolver variableResolver, FunctionMapper functionMapper) {
        ExpressionEvaluator ee = new ExpressionEvaluatorImpl();
        try {
            Set keys = items.keySet();
            boolean notempty = true;
            readFile:
            while (true) {
                String line = r.readLine();
                if (line == null) break;
                if (line.length() == 0) {
                    w.newLine();
                    continue;
                }

                if (notempty)
                for (Iterator it = keys.iterator(); it.hasNext(); ) {
                    String nm = (String)it.next();
                    ConfigItem item = (ConfigItem)items.get(nm);
                    String pattern = item.getPath();
                    if (pattern.indexOf("${") >= 0)
                        pattern = (String)ee.evaluate(pattern, String.class, variableResolver, functionMapper);
                    if (!line.matches(pattern)) continue;

                    if (item.getAction() == ConfigItem.ACTION_REMOVE)
                        continue readFile;

                    String value = item.getValue();
                    for (Iterator itv = item.getConditionVals().iterator(), itt = item.getConditionTests().iterator(); itv.hasNext() && itt.hasNext(); ) {
                        String v = (String)itv.next();
                        String t = (String)itt.next();
                        Boolean cond = (Boolean) ee.evaluate(t, Boolean.class, variableResolver, functionMapper);
                        if (cond != null && cond.booleanValue()) {
                            value = v; break;
                        }
                    }
                    if (value.indexOf("${") >= 0)
                        value = (String)ee.evaluate(value, String.class, variableResolver, functionMapper);
                    
                    line = line.replaceAll(pattern, value);
                    if (item.isRequired()) {
                        keys.remove(nm);
                        notempty = !keys.isEmpty();
                    }
                    break;
                }

                w.write(line);
                w.newLine();
            }
            return true;
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (ELException ele) {
            ele.printStackTrace();
        }
        return false;
    }

    protected void readItems(Node config) {
        Element cfg = (Element)config;
        path = cfg.getAttribute("path");
        if ("".equals(path))
            throw new IllegalArgumentException("Path is required for xml-config '"+getName()+"'");
        encoding = cfg.getAttribute("encoding");
        if (encoding.length() == 0) encoding = null;
        
        for (Node child = config.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getNodeType() != Node.ELEMENT_NODE) continue;
            if ("item".equals(child.getNodeName())) {
                ConfigItem item = readItem((Element)child);
                if (item != null) items.put(item.getPath(), item);
            }
        }
    }

    private ConfigItem readItem(Element el) {
        ConfigItem item = new ConfigItem();
        for (Node child = el.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getNodeType() != Node.ELEMENT_NODE) continue;
            final String name = child.getNodeName();
            if ("pattern".equals(name)) {
                String nm = getElementValue(child);
                item.setPath(nm);
            }
            else if ("replace".equals(name)) {
                String cond = ((Element)child).getAttribute("when");
                if (cond.length() > 0)
                    item.addConditionValue(getElementValue(child), cond);
                else
                    item.setValue(getElementValue(child));
            }
            else if ("action".equals(name))
                item.setAction(getElementValue(child));
            else if ("condition".equals(name))
                item.setCondition(getElementValue(child));
        }
        if (item.getPath() == null) return null;
        item.setRequired("true".equalsIgnoreCase(el.getAttribute("replace-first")));
        return item;
    }

    public boolean skip(VariableResolver variableResolver, FunctionMapper functionMapper) {
        if (super.skip(variableResolver, functionMapper)) return true;
        return items.isEmpty();
    }

}
